/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.kramerius.rukopisyimport.foxml;

import cz.mzk.kramerius.commons.DatastreamId;
import cz.mzk.kramerius.commons.Dom4jUtils;
import cz.mzk.kramerius.commons.Model;
import cz.mzk.kramerius.commons.Namespaces;
import cz.mzk.kramerius.imports.Datastream;
import cz.mzk.kramerius.imports.DatastreamFactory;
import cz.mzk.kramerius.imports.FoxmlHelper;
import cz.mzk.kramerius.imports.Monograph;
import cz.mzk.kramerius.imports.Policy;
import cz.mzk.kramerius.rukopisyimport.ConversionLog;
import cz.mzk.kramerius.rukopisyimport.ManuscriptCollection;
import cz.mzk.kramerius.rukopisyimport.MonographData;
import cz.mzk.kramerius.rukopisyimport.sourceData.MarcDocument;
import cz.mzk.kramerius.rukopisyimport.sourceData.TeiDocument;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.QName;
import org.dom4j.XPath;
import org.joda.time.LocalDate;

/**
 *
 * @author Martin Rehanek (rehan@mzk.cz)
 */
public class MonographBuilder {

    private static final Logger logger = Logger.getLogger(MonographBuilder.class.getName());
    private final Document marc2dcXslt;
    private final Document tei2modsXslt;
    private final String krameriusUrl;
    private final String alephUrl;
    private XPath modsXpath = Dom4jUtils.createXPath("/mods:modsCollection/mods:mods");
    private XPath locationXpath = Dom4jUtils.createXPath("/mods:modsCollection/mods:mods/mods:location");
    private XPath shelfLocatorXpath = Dom4jUtils.createXPath("mods:shelfLocator");
    private XPath copyInformationXpath = Dom4jUtils.createXPath("//mods:location/mods:holdingSimple/mods:copyInformation");
    private XPath recordInfoXpath = Dom4jUtils.createXPath("//mods:recordInfo");

    /**
     *
     * @throws FileNotFoundException if some xsl template file cannot be found
     * @throws DocumentException in case of error in loading xsl template
     */
    public MonographBuilder(File monographMarc2dcXsltFile, File monographTei2modsXsltFile, String krameriusUrl, String alephUrl) throws FileNotFoundException, DocumentException, IOException {
        this.marc2dcXslt = Dom4jUtils.loadDocument(monographMarc2dcXsltFile, true);
        this.tei2modsXslt = Dom4jUtils.loadDocument(monographTei2modsXsltFile, true);
        this.krameriusUrl = krameriusUrl;
        this.alephUrl = alephUrl;
    }

    /**
     *
     * @param data
     * @param conversionLog
     * @return pid of the monograph
     */
    public String createAndSaveMonograph(MonographData data, ConversionLog conversionLog, String monographUuid, Model model) {
        if (monographUuid == null) {
            monographUuid = FoxmlHelper.generateUuid();
        }
        Monograph monograph = createMonograph(data.getTei(), data.getMarc(), data.getCollection(), conversionLog, monographUuid, model);
        conversionLog.logMonograph(monograph);
        conversionLog.setPages(data.getPageList());
        conversionLog.setInternalParts(data.getInternalPartList());
        Datastream relsExt = createRelsExtDatastream(monograph, data.getInternalPartList(), data.getPageList(), model);
        monograph.addDatastream(relsExt);
        FoxmlHelper.saveFoxml(data.getConversionDir(), monograph);
        return monograph.getPid();
    }

    private Monograph createMonograph(TeiDocument tei, MarcDocument marc, ManuscriptCollection col, ConversionLog log, String uuid, Model model) {
        Document modsDoc = createModsDoc(tei, marc, col, uuid);
        Monograph monograph = new Monograph(uuid, findLabel(modsDoc));
        String signature = tei.findSignature();
        monograph.setSignature(signature);
        String sysno = marc.findSysno();
        monograph.setSysno(sysno);
        Datastream mods = createModsDatastream(monograph, modsDoc);
        monograph.addDatastream(mods);
        Datastream dc = createDcDatastream(monograph, marc, model);
        monograph.addDatastream(dc);
        //tei datastream musi byt posledni (protoze se rootElement teiDoc odpoji a pripoji do foxml)
//        Datastream teiDs = createTeiDatastream(tei);
//        monograph.addDatastream(teiDs);
        Datastream policy = FoxmlHelper.createPolicyDatastream(Policy.PUBLIC);
        monograph.addDatastream(policy);
        return monograph;
    }

    private Document createModsDoc(TeiDocument tei, MarcDocument marc, ManuscriptCollection col, String uuid) {
        Document modsDoc = Dom4jUtils.transformDocument(tei.getDocument(), tei2modsXslt);
        updateModsDoc(modsDoc, marc, col, uuid);
        FoxmlHelper.removeSchema(modsDoc);
        return modsDoc;
    }

    private String findLabel(Document modsDoc) {
        String query = "/mods:modsCollection/mods:mods/mods:titleInfo/mods:title";
        XPath xpath = Dom4jUtils.createXPath(query);
        Node title = xpath.selectSingleNode(modsDoc);
        if (title == null) {
            logger.log(Level.WARNING, "no element ''{0}'' found", query);
            return "";
        } else {
            return title.getText();
        }
    }

    private Datastream createModsDatastream(Monograph monograph, Document modsDoc) {
        if (modsDoc == null) {
            logger.log(Level.SEVERE, "cannot create mods datastream for monograph with pid ''{0}''", monograph.getPid());
            return null;
        }
        return DatastreamFactory.createInlineDatastream(modsDoc, DatastreamId.BIBLIO_MODS);
    }

    private Datastream createDcDatastream(Monograph monograph, MarcDocument marc, Model model) {
        Document dcDoc = Dom4jUtils.transformDocument(marc.getDocument(), marc2dcXslt);
        FoxmlHelper.removeSchema(dcDoc);
        updateDcDoc(dcDoc, monograph, model);
        if (dcDoc == null) {
            logger.log(Level.SEVERE, "cannot create dublin core datastream for monograph with pid ''{0}''", monograph.getPid());
        }
        return DatastreamFactory.createInlineDatastream(dcDoc, DatastreamId.DC);
    }

    private Datastream createTeiDatastream(TeiDocument tei) {
        Document doc = tei.getDocument();
        return DatastreamFactory.createInlineDatastream(doc, DatastreamId.TEI);
    }

    private Datastream createRelsExtDatastream(Monograph monograph, List<InternalPart> internalPartList, List<Page> pageList, Model model) {
        Document doc = FoxmlHelper.createRelsExtSkeleton(monograph, model.toString(), Policy.PUBLIC);
        for (Page page : pageList) {
            FoxmlHelper.addRelationshipToRelsExt(doc, page, "hasPage");
        }
        for (InternalPart internalPart : internalPartList) {
            FoxmlHelper.addRelationshipToRelsExt(doc, internalPart, "hasIntCompPart");
        }
        return DatastreamFactory.createInlineDatastream(doc, DatastreamId.RELS_EXT);
    }

    private void updateDcDoc(Document dcDoc, Monograph monograph, Model model) {
        Element dcRootEl = dcDoc.getRootElement();
        Element idUuid = dcRootEl.addElement("dc:identifier");
        idUuid.addText(monograph.getPid());
//        Element idContract = dcRootEl.addElement("dc:identifier");
//        idContract.addText("contract:TODO");
        //TODO: element contributor. Udajne povinny, ale ve skutecnosti neni vsude pritomny
        //TODO: identifier - isbn / extid - isbn rukopisy nemaji (jen veci od roku 89)
        //TODO: subject - ddc
        //TODO: subject - udc
        String signature = monograph.getSignature();
        if (signature != null) {
            Element idSignature = dcRootEl.addElement("dc:identifier");
            idSignature.addText("signature:" + signature);
        }
        String sysno = monograph.getSysno();
        if (sysno != null) {
            Element idSysno = dcRootEl.addElement("dc:identifier");
            idSysno.addText("sysno:" + sysno);
        }
        removeDcTypeElements(dcDoc);
        Element typeEl = dcRootEl.addElement("dc:type");
        typeEl.addText("model:" + model.toString());
        Element rightsEl = dcRootEl.addElement("dc:rights");
        rightsEl.addText("policy:" + Policy.PUBLIC.toString().toLowerCase());
        updateDcLanguages(dcDoc);
    }

    private void removeDcTypeElements(Document doc) {
        XPath typeXpath = Dom4jUtils.createXPath("/oai_dc:dc/dc:type");
        List<Node> nodes = typeXpath.selectNodes(doc);
        for (Node node : nodes) {
            node.detach();
        }
    }

    private void updateModsLanguages(Document modsDoc) {
        XPath languageXpath = Dom4jUtils.createXPath("/mods:mods/mods:language/mods:languageTerm");
        updateLanguages(modsDoc, languageXpath);
    }

    private void updateDcLanguages(Document dcDoc) {
        XPath languageXpath = Dom4jUtils.createXPath("/oai_dc:dc/dc:language");
        updateLanguages(dcDoc, languageXpath);
    }

    private void updateLanguages(Document doc, XPath xpath) {
        List<Node> nodes = xpath.selectNodes(doc);
        for (Node languageNode : nodes) {
            Element languageEl = (Element) languageNode;
            String originalLang = languageEl.getTextTrim();
            languageEl.clearContent();
            languageEl.addText(transformLanguage(originalLang));
        }
    }

    private String transformLanguage(String originalLang) {
        if ("d".equals(originalLang)) {
            return "ger";
        }
        return originalLang;
    }

    private void updateModsDoc(Document modsDoc, MarcDocument marc, ManuscriptCollection col, String uuid) {
        //addPhysicalLocation(modsDoc, marc);
        addUdcOrDdc(modsDoc, marc);
        addTopic(modsDoc, marc);
        addSysno(modsDoc, marc);
        addLinks(modsDoc, marc, uuid);
        //addPublishment(modsDoc, marc);
        addRelatedItem(modsDoc, col);
        updateRecordInfo(modsDoc, uuid);
    }

    private void addPhysicalLocation(Document modsDoc, MarcDocument marc) {
        String location = marc.find040a();
        if (location != null) {
            Element locationEl = (Element) locationXpath.selectSingleNode(modsDoc);
            Element shelfLocatorEl = (Element) shelfLocatorXpath.selectSingleNode(locationEl);
            String shelfLocatorStr = shelfLocatorEl.getTextTrim();
            shelfLocatorEl.detach();
            Element physicalLocationEl = locationEl.addElement(new QName("physicalLocation", Namespaces.mods));
            physicalLocationEl.addText(location);
            shelfLocatorEl = locationEl.addElement(new QName("shelfLocator", Namespaces.mods));
            shelfLocatorEl.addText(shelfLocatorStr);
        }
    }

    private void addUdcOrDdc(Document modsDoc, MarcDocument marc) {
        List<String> udcs = marc.find080a();
        for (String udc : udcs) {
            Element modsEl = (Element) modsXpath.selectSingleNode(modsDoc);
            Element classificationEl = modsEl.addElement(new QName("classification", Namespaces.mods));
            classificationEl.addAttribute("authority", "udc");
            classificationEl.addText(udc);
        }
    }

    private void addTopic(Document modsDoc, MarcDocument marc) {
        String topic = marc.find650a();
        if (topic != null) {
            Element modsEl = (Element) modsXpath.selectSingleNode(modsDoc);
            Element subjectEl = modsEl.addElement(new QName("subject", Namespaces.mods));
            Element topicEl = subjectEl.addElement(new QName("topic", Namespaces.mods));
            topicEl.addText(topic);
        }
    }

    private void addSysno(Document modsDoc, MarcDocument marc) {
        String sysno = marc.findSysno();
        if (sysno != null) {
            Element modsEl = (Element) modsXpath.selectSingleNode(modsDoc);
            Element identifierEl = modsEl.addElement(new QName("identifier", Namespaces.mods));
            identifierEl.addAttribute("type", "sysno");
            identifierEl.addText(sysno);
        }
    }

    private void addLinks(Document modsDoc, MarcDocument marc, String uuid) {
        Element copyInformation = (Element) copyInformationXpath.selectSingleNode(modsDoc);
        copyInformation.addElement(new QName("electronicLocator", Namespaces.mods));
        copyInformation.addText(alephLink(marc));

        Element location = (Element) locationXpath.selectSingleNode(modsDoc);
        Element url = location.addElement(new QName("url", Namespaces.mods));
        url.addText(krameriusLink(uuid));
    }

    private String alephLink(MarcDocument marc) {
        String sysno = marc.findSysno();
        return alephUrl
                + "/F?func=direct&doc_number="
                + sysno
                + "&local_base=MZK03&format=999";
    }

    private String krameriusLink(String uuid) {
        return krameriusUrl
                + "/search/handle/uuid:"
                + uuid;
    }

    private void addPublishment(Document modsDoc, MarcDocument marc) {
        Element modsEl = (Element) modsXpath.selectSingleNode(modsDoc);
        Element originInfoEl = modsEl.addElement(new QName("originInfo", Namespaces.mods));
        originInfoEl.addAttribute("transliteration", "publisher");
        addPublishmentPlace(originInfoEl, marc);
        addPublisher(originInfoEl, marc);
        addPublishmentDate(originInfoEl, marc);
    }

    private void addPublishmentPlace(Element originInfoEl, MarcDocument marc) {
        String place = marc.find260aCorrected();
        if (place != null) {
            Element placeEl = originInfoEl.addElement(new QName("place", Namespaces.mods));
            Element placeTerm = placeEl.addElement(new QName("placeTerm", Namespaces.mods));
            placeTerm.addAttribute("type", "text");
            placeTerm.addText(place);
        }
    }

    private void addPublisher(Element originInfoEl, MarcDocument marc) {
        String name = marc.find260bCorrected();
        if (name != null) {
            Element publisher = originInfoEl.addElement(new QName("publisher", Namespaces.mods));
            publisher.addText(name);
        }
    }

    private void addPublishmentDate(Element originInfoEl, MarcDocument marc) {
        String dates = marc.find260c();
        if (dates != null) {
            Element dateIssued = originInfoEl.addElement(new QName("dateIssued", Namespaces.mods));
            dateIssued.addText(dates);
        }
    }

    private void addRelatedItem(Document modsDoc, ManuscriptCollection col) {
        Element modsEl = (Element) modsXpath.selectSingleNode(modsDoc);
        Element relatedItem = modsEl.addElement(new QName("relatedItem", Namespaces.mods));
        relatedItem.addAttribute("type", "series");
        Element titleInfo = relatedItem.addElement(new QName("titleInfo", Namespaces.mods));
        Element title = titleInfo.addElement(new QName("title", Namespaces.mods));
        title.addText(col.getDescription());
    }

    private void updateRecordInfo(Document modsDoc, String uuid) {
        Element recordInfo = (Element) recordInfoXpath.selectSingleNode(modsDoc);
        LocalDate now = new LocalDate();

        String nowStr = now.toString();
        Element creationDate = recordInfo.addElement(new QName("recordCreationDate", Namespaces.mods));
        creationDate.addAttribute("encoding", "iso8601");
        creationDate.addText(nowStr);
        Element changeDate = recordInfo.addElement(new QName("recordChangeDate", Namespaces.mods));
        changeDate.addAttribute("encoding", "iso8601");
        changeDate.addText(nowStr);
        Element recordId = recordInfo.addElement(new QName("recordIdentifier", Namespaces.mods));
        recordId.addText("uuid:" + uuid);
    }
}
